完整遊戲開發及自製簡易Buff系統-無限繼承-開發總結

完整遊戲開發及自製簡易Buff系統-無限繼承-開發總結

DEMO

DEMO 影片

github連結

(一) 概要

在第二款遊戲中,我的預計是要完成一款真的可以玩的遊戲,而非單單一個試驗品。因此,我決定採用無線波數這一個概念。遊戲中僅有一個場景,但在擊敗一波怪物後下一波便會出現,無限制的重複下去。而為了使這樣的遊戲充滿隨機性,以及不使人快速地感到無趣。我添加了隨機Buff系統,以及隨機怪物生成點等隨機要素。且透過這些Buff的組合,還能誕生出不同的玩法。

不過,可惜之處在於,並未曾查詢過相關於遊戲數據的書籍,以及上過相關遊戲設計課程,導致我在設計遊戲數值時,產生難度不一的情形。此外遊戲中的Buff也並未將機率有計畫性的分布,導致某些強力的Buff不斷出現,玩家強度超出預期。但這一點,也在下個遊戲中得到了改善。

(二) 遊戲玩法

透過上下左右進行移動,滑鼠左鍵攻擊、右鍵進行衝鋒,S鍵存檔、P鍵暫停。每次擊敗一波後,會得到選擇Buff的機會。每十關Boss會出現一次。普通的小怪僅會透過撞擊造成傷害,但Boss有不同的攻擊模式。

Boss攻擊模式,每五秒發起一次攻擊,瞬移至玩家身邊,揮刀斬擊,命中一次後,會馬上揮出第二刀,如果第二刀揮中,會再揮出第三刀。第一刀無特殊效果,第二刀命中會回復大量生命,第三刀命中會造成超強擊退。且Boss會召喚小怪,小怪不會發起攻擊,但每隻小怪,會為Boss提供強化。

(三) 腳色操作_攻擊、衝鋒(腳色池)

在這一款遊戲中,為了達成擊敗敵人的效果,我為玩家添加了更實用的攻擊方式,斬擊。透過點擊鼠標左鍵,使斬擊的動畫出現,並在斬擊的動畫上添加Istrigger,使斬擊在接觸已放置碰撞體的敵人時,會出發,從而進行扣除敵人生命的程式。

相對的,一旦敵人的攻擊方式接觸到玩家,也會觸發玩家身上的碰撞器,從而啟動玩家身上扣除生命值的程式。

至於衝鋒的技能,事實上就是在短暫的時間內,提升玩家的移動速度,但為了造成玩家的回饋心理,因此設計了殘影這一要素,使玩家感受的到,「我衝鋒了」這一反饋。至於殘影,則使用對象池來實現,透過對象池得好處是能省下很多的運行浪費,且生成的物件可以重複利用。在不足以應付當前需要的數量時,則能再動態添加。

void Awake()  
    {  
        instance = this;  
  
        //初始化對象池  
        FillPool();  
    }  
  
    public void FillPool()  
    {  
        for (int i = 0; i < shadowCount; i++)  
        {  
            var newShadow = Instantiate(shadowPrefab);  
               newShadow.transform.SetParent(transform);  
  
            //取消啟用,返回對象池  	                                     
            ReturnPool(newShadow);  
        }  
    }  
  
    public void ReturnPool(GameObject gameObject)  
    {  
        gameObject.SetActive(false);  
        availableObjects.Enqueue(gameObject);  
    }  
  
    public GameObject GetFromPool()  
    {  
        if(availableObjects.Count == 0)  
        {  
            FillPool();  
        }  
        var outShadow = availableObjects.Dequeue();  	  
        outShadow.SetActive(true);  
        
        return outShadow;  
    }

(圖十一) 腳色池程式碼(New RPG game\Assets\Scripts\ShadowPool.cs)。
腳色池作為殘影只是它的一個應用,用來作為射擊遊戲中的子彈,應該是較常見的用法。


(圖十二)腳色衝鋒示意圖。

(四) 敵人AI

而敵人AI也在這款遊戲中被進一步強化,雖然不具備自動尋路的能力,但也能朝著玩家的座標不斷靠近。此外,也為敵人添加了隨關卡數增加,數值隨之增加的程式。

而Boss則是這一次我嘗試著挑戰的項目,我在Boss的腳本中,設計了三段式的攻擊、瞬移的移動方式以及召喚小怪的能力。

也在Boss的頭上增加了一個倒數計時器,以方便玩家觀察Boss的攻擊模式,隨著計時器數字減少,顯示的數字顏色也會更加具有危險性。


(圖十三)Boss示意圖。

IEnumerator Ad()  
    {  
        PlayerHealth player = other.gameObject.GetComponent<PlayerHealth>();  
  
        if (attack == 0)  
        {  
            yield return new WaitForSeconds(0.1f);  
            Attack();  
            yield return new WaitForSeconds(0.4f);  
            if (attack == 1)  
            {  
                if(hit==1)  
                {  
                    PlayerHealth.hp -= atk;  
  
                    bossDamage = atk;  
                    GameObject.Find("Player").SendMessage("BossAttack");                     
                      
                    set = 1;  
                    StartCoroutine(Set());  
                    Attack();  
                    yield return new WaitForSeconds(0.4f);  
                    if(hit==2)  
                    {  
                        PlayerHealth.hp -= atk + 5;  
  
                        bossDamage = atk + 5;  
                        GameObject.Find("Player").SendMessage("BossAttack");  
  
                        Boss10.hp += (atk + 5) * 35 * (ClassManager.levelNum % 10) * 6;  
 	                        DamageNum damagable = Instantiate(damageCanvas, transform.position,       Quaternion.identity).GetComponent<DamageNum>();  
                        damagable.ShowUIDamage(Mathf.RoundToInt((atk + 5) * 10000), 0);  
  
                        Attack();  
                        yield return new WaitForSeconds(0.4f);  
                        if(hit==3)  
                        {  
                               PlayerHealth.hp -= Boss10_Rank1.atk + 10;  	  
                            bossDamage = atk + 10;  
                            GameObject.Find("Player").SendMessage("BossAttack");  
                              
                            hitAfter = 1;  
                        }  
                    }  
                }  
            }  
        }

(圖十四)Boss攻擊模式程式碼
(New RPG game\Assets\Scripts\Boss10_Rank1.cs)。

(五) UI介面

遊戲介面同樣有暫停,除此之外,更添加了buff的顯示區塊,關卡的提示等。而玩家身上的血條UI,則是透過三張不同顏色的圖片,設置成填充模式以達成。至於扣血效果的UI則製成了預製體,需要時調用。最後則是分數計算的顯示,跟上一款遊戲的收集櫻桃是類似的。



(圖十五、十六、十七)UI介面。
此外,也於遊戲中製作了預製體的扣血效果,並於怪物或玩家受到傷害時,生成於玩家或是怪物的身旁。而Boss身上則有額外的綠色效果,作為回血的數值。

(圖十八)回復以及扣血效果。

(六) 程式碼_無限波數、Buff系統、存檔系統

為了提升遊戲的可玩性,以及使遊戲的紀錄可被儲存,我上網查詢有關於這方面的教學影片以及文章。三者之中,最容易達成的是無限波數的部分,透過程式碼中的變數調控,以檢測場上敵人的數目來決定是否進入下一波。

一旦進入下一波,再透過隨機生成的方式,隨機生成敵人到場上,每十關出現一次Boss。

private void Update()  
    {        
        if (enemy == 0) //召喚怪物  
        {  
            enemyCount = (ClassManager.levelNum-1) * 1 % 5;  
            if(enemyCount==0)  
            {  
                Appear();  
            }  
  
            for (int i = 0; i < enemyCount; i++)  
                Appear();  
            }  
            enemy = 1;  
        }  
          
        Record();  
    }  
  
    void Appear() //隨機出現位置  
    {  
        float x = Random.Range(0, 10);  
        float y = Random.Range(0, 10);  
  
        if ((ClassManager.levelNum % 10) != 1)  
        {  
            point.transform.position = new Vector2(point.transform.position.x + x, point.transform.position.y + y);  
            Instantiate(enemyBat, point.transform.position, enemyBat.transform.rotation);  
            point.transform.position = new Vector2(point.transform.position.x - x, point.transform.position.y - y);  
            Instantiate(enemyBeast, point.transform.position, enemyBeast.transform.rotation);  
  
            nowEnemyCount += 2;  
        }else if((ClassManager.levelNum%10)==1)  
        {  
            point.transform.position = new Vector2(point.transform.position.x - x, point.transform.position.y - y);  
            Instantiate(boss10, point.transform.position, boss10.transform.rotation);  
              
            nowEnemyCount += 1;  
        }  
    }  
  
    void Record() //重新激活  
    {  
        if (nowEnemyCount == 0 && run == 0)  
        {  
            if(score==1)  
            {  
                ScoreInformation.scorePower += timeScore * 10 * ((ClassManager.levelNum / 2) + 1);  
                timeScore = 50;  
            }  
  
            GameObject.Find("GameStartCanvas").SendMessage("Run");  
            run = 1;  
            score = 1;  
        }  
    }

(圖十九)敵人生成及波數程式碼
(New RPG game\Assets\Scripts\EnemyAppearManager.cs)。

至於存檔機制則是因為網路上有種類繁多的教程,因此在實作過程中,沒有遇到特別多的困難。最後我選擇PlayerPrefs作為存檔的方式,但在下一款遊戲中,我將PlayerPrefs、序列化:二進制、序列化:JSON、序列化:XML都學習過了,後邊會提到。

maxHp = PlayerHealth.maxNowHp;  
PlayerPrefs.SetFloat("maxHp", maxHp);  
  
maxHp = PlayerPrefs.GetFloat("maxHp");

(圖二十)PlayerPrefs存檔方式(New RPG game\Assets\Scripts\Load01.cs)。

最後的Buff系統則是困擾了我數天,其原因在於網路上並沒有相關的教程。因此,我開始構思一個能隨機產生Buff並給予玩家選擇的機制。

在我看來,Buff系統的核心有三個,資料庫、管道、生成。

資料庫中儲存有關於這個Buff的資料,玩家在選擇時,資料便會出現,提供玩家有關於這個Buff的能力。管道則是將程式裡的Buff系統實際顯示在遊戲屏幕上的方式,這裡我使用了UI以及隨機取數來完成,隨機生成三個可供選擇的Buff。最後的生成,則是使效果生成,只需要在程式碼中修改相關的變數即可。

Buff系統本體:
(New RPG game\Assets\Scripts\Buff.cs)。
Buff系統資料庫:
(New RPG game\Assets\Scripts\BuffData.cs)。
在實作Buff機制時,遇到了十分大的挫折,光是Buff系統的構思,便出錯了數次,導致出現奇怪的結果。但更多的是沒有任何反應,時間也一分一秒的在減少,那時候每天都是早上8點起床,坐在電腦前實作到晚上12點。

雖然實際上的程式碼不多,似乎不需要那麼久的時間,但浪費最多時間的是重覆的試驗。

除此之外,也不斷查詢是否有相關的資料能幫助我完成這項任務,儘管最後是靠著靈光一閃解決了這個問題,但如果沒有那些天累積的知識,這個點子也不會出現。

也是在這次的Buff系統過後,我開始真正掌握到編寫遊戲程式的感覺,以及真正開發著一款遊戲。

(七) 子彈功能_追蹤彈

這款遊戲最後要提到的就是追蹤彈的功能。在實作遊戲時,我常會到各個影片網站尋找對開發有幫助的影片,也是因此,意外的發現了有關於子彈追蹤的影片。實際上,如果要做出簡單的追蹤子彈效果,使用Trail就能完成。

但僅僅是子彈朝著敵人直線射擊過去,就少了一點味道,原先是不打算製作的。只是,那部影片與子彈軌跡有關,因此,在看到那個影片後,我才決定製作這一功能。

不過,這項功能的開發只算是作為研究的目的,並沒有將其實裝在遊戲中的想法,因此,實際的遊戲中需要啟動隱藏的測試按鈕,才能使用。

之所以不實裝的原因之一,是因為Buff系統的協調出了點問題,調適需要一段時間。其次,則是我當時對於這個功能的想法並不多,所以沒有下手大改這款遊戲,畢竟,本來這款遊戲的目的是設計為一款近戰遊戲。

以下是開啟射擊模式的方法:進入暫停介面,需要將鼠標移到黑塊的相應位置處,黑塊才會顯示。

(圖二十一)射擊模式開啟方法。

(圖二十二)射擊模式示意圖。

在這個系統中,可以透過調整角度的係數,從而實現不同的子彈軌跡,以下是另一種不同的軌跡示範。

(圖二十三)類似閃電的子彈軌跡示意圖。

public void Chase()  
    {  
        Vector3 direction = target.transform.position - this.transform.position; //朝向目標  
        float ang = 360 - Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg; //方向向量轉角度  
        transform.eulerAngles = new Vector3(0, 0, ang); //角度設為對應角度  
  
        this.transform.rotation = this.transform.rotation * Quaternion.Euler(0, 0, rotationAngle);  
        transform.Translate(Vector3.up * bulletSpeed * Time.deltaTime);  
    }

(圖二十四)實現子彈軌跡的程式碼
(New RPG game\Assets\Scripts\NormalBullet.cs)。

透過在這個程式碼中調整係數,便能做到不同的子彈軌跡效果。雖然這個功能在這款遊戲裡並沒有正式加入,但也多虧這個功能的啟發,讓我有了製作下一款遊戲的念頭,製作一款射擊類遊戲。

(八)參考資料


完整遊戲開發及自製簡易Buff系統-無限繼承-開發總結
https://z-hwa.github.io/webHome/[object Object]/Game-develope/完整遊戲開發及自製簡易Buff系統-無限繼承-開發總結/
作者
crown tako
發布於
2021年10月6日
許可協議